fix(android): fix Google Pay crash — Activity context and ActivityEventListener#49
Conversation
…ng ActivityEventListener
- Pass Activity (not ApplicationContext) to Wallet.getPaymentsClient() so the
payment sheet has a window to attach to, fixing the "not attached to an
Activity" crash on button tap
- Implement ActivityEventListener and register it in init{} so onActivityResult
is forwarded by the RN bridge — previously the pending Promise would hang
forever as the result was never received
- Make handlePaymentResult private now that it is called internally via the
listener rather than from MainActivity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Use the correct x-publishable-key header instead of merchant_token when fetching the Google Pay APM config from Bolt's API. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Fixes Android Google Pay crash/hanging flows by ensuring the Google Pay SDK is invoked with an Activity context and by wiring onActivityResult delivery to the native module; also corrects the HTTP header used to fetch the Google Pay APM config on the JS side.
Changes:
- Android:
PaymentsClientis created with anActivitycontext and the module is registered as anActivityEventListenerto receiveonActivityResult. - Android: payment-result handling is routed through the module’s
onActivityResultand internalized (private). - JS: APM config fetch now sends
x-publishable-keyinstead ofmerchant_token.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/payments/GoogleWallet.tsx | Fixes the request header used to fetch Google Pay APM config. |
| android/src/main/java/com/boltreactnativesdk/GooglePayModule.kt | Uses Activity context for Google Pay and registers for activity results to resolve/reject pending promises. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
android/src/main/java/com/boltreactnativesdk/GooglePayModule.kt
Outdated
Show resolved
Hide resolved
src/payments/GoogleWallet.tsx
Outdated
| const response = await fetch(`${apiUrl}/v1/apm_config/googlepay`, { | ||
| method: 'GET', | ||
| headers: { | ||
| merchant_token: publishableKey, | ||
| 'x-publishable-key': publishableKey, | ||
| }, |
There was a problem hiding this comment.
This change fixes the request header name used for the APM config fetch, but there’s no unit test asserting the request is sent with the expected header. Since this header is required for the config call to succeed, consider adding a Jest test that mocks fetch and verifies the x-publishable-key header is used.
There was a problem hiding this comment.
Fixed — added two tests to GoogleWallet.test.tsx that mock fetch and assert directly against fetchGooglePayAPMConfig: one verifying the correct header is passed, one verifying a non-ok response throws.
- Capitalize header to 'X-Publishable-Key' per convention (SanthoshCharanBolt) - Add Bolt.apiHeaders() method to centralise the header for all API calls and use it in fetchGooglePayAPMConfig (SanthoshCharanBolt) - Don't cache PaymentsClient — create per-call to avoid holding a stale Activity reference across rotation/recreation (Copilot) - Override invalidate() to call removeActivityEventListener, preventing stale listeners on React instance teardown (Copilot) - Clear pending fields before NO_ACTIVITY early return to avoid retaining references longer than necessary (Copilot) - Add test for Bolt.apiHeaders() returning X-Publishable-Key (Copilot) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| } | ||
| return paymentsClient!! | ||
| val walletOptions = Wallet.WalletOptions.Builder() | ||
| .setEnvironment(WalletConstants.ENVIRONMENT_TEST) |
There was a problem hiding this comment.
Is this correct? We are setting env to test all the time?
There was a problem hiding this comment.
Good catch — fixed. Bolt.ts now exposes environment as a public field. buildNativeConfig maps it to googlePayEnvironment: 'PRODUCTION' | 'TEST' and passes it through the config JSON. GooglePayModule.kt has a new walletEnvFromConfig() helper that reads it and maps to WalletConstants.ENVIRONMENT_PRODUCTION / ENVIRONMENT_TEST — used by both isReadyToPay and requestPayment.
Replaces the hardcoded WalletConstants.ENVIRONMENT_TEST with a value
derived from the JS-side Bolt environment:
- production → ENVIRONMENT_PRODUCTION
- sandbox / staging → ENVIRONMENT_TEST
Changes:
- Bolt.ts: expose `environment` as a public readonly field
- GoogleWallet.tsx: pass `googlePayEnvironment` ("PRODUCTION"|"TEST")
through buildNativeConfig so the native module can read it from configJson
- GooglePayModule.kt: walletEnvFromConfig() parses configJson and maps to
WalletConstants; both isReadyToPay and requestPayment use it
- GoogleWallet.test.tsx: add fetchGooglePayAPMConfig tests verifying the
correct header is sent and that non-ok responses throw; update useBolt
mock to include apiHeaders/environment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The exhaustive-deps rule flagged bolt.environment being used inside the isReadyToPay useEffect without being listed as a dependency. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The react-native Jest preset resolves .ios.tsx before .tsx, so importing from 'GoogleWallet' in tests loaded the iOS stub which only exports GoogleWallet — not fetchGooglePayAPMConfig — causing 'is not a function'. Moving the function to a platform-agnostic googlePayApi.ts lets the test import it directly. GoogleWallet.tsx re-exports it to keep the public API unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t.tsx Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Description
Fix two bugs that caused Google Pay to crash and a third that sent the wrong auth header when fetching the Bolt APM config.
1.
PaymentsClientcreated with wrong context (GooglePayModule.kt)Wallet.getPaymentsClient()was called withreactApplicationContext(the Application context). The Google Pay SDK needs anActivitycontext to attach the payment sheet to a window — henceTried to show an alert while not attached to an Activity.2.
onActivityResultnever received (GooglePayModule.kt)GooglePayModulenever registered as anActivityEventListener, so the result from the Google Pay sheet was silently swallowed and the pendingPromisewould hang forever. The module now implementsActivityEventListenerand registers itself ininit{}— no changes toMainActivityrequired.3. Wrong header when fetching APM config (
GoogleWallet.tsx)The
fetchGooglePayAPMConfigrequest usedmerchant_tokenas the header name instead of the correctx-publishable-key, causing the config fetch to fail before the payment sheet could even be shown.Testing
onCompletecallback fires with token + billing addressonErrorcallback fires withCANCELLEDNO_ACTIVITYrejectionSecurity Review
Important
A security review is required for every PR in this repository to comply with PCI requirements.
Security Impact Summary
No new data flows or secrets introduced. The
x-publishable-keyheader fix corrects which key is sent to the Bolt APM config endpoint — the key itself was already present in the original code, just under the wrong header name. The Activity context change only affects where Google Pay's system UI is anchored; no payment data handling is altered.